use axum::{routing::post, Router};
use bytes::BufMut;
use serde::Deserialize;

use crate::{
    error::AppErr,
    serialport::{self, address, cmd, BodyEncode},
    serve::{
        api::cbor_ok,
        utils::{Cbor, Void},
    },
};

#[derive(Deserialize)]
struct PickCtlReq {
    index: u8,
    position: i32,
}

async fn pick_ctrl(req: Cbor<PickCtlReq>) -> Result<Void, AppErr> {
    let cp_cargo = match req.index {
        1 => address::CARGO1,
        2 => address::CARGO2,
        _ => return Err(AppErr::Static("invalid index")),
    };
    serialport::req(cp_cargo, cmd::cargo::PICK_CTRL, &req.position)
        .await?
        .res_result()?;
    Ok(Void)
}

#[derive(Deserialize)]
struct ComprCtrlReq {
    index: u8,
    position: u8,
}

async fn compr_ctrl(req: Cbor<ComprCtrlReq>) -> Result<Void, AppErr> {
    let cp_cargo = match req.index {
        1 => address::CARGO1,
        2 => address::CARGO2,
        _ => return Err(AppErr::Static("invalid index")),
    };

    serialport::req(cp_cargo, cmd::cargo::COMPR_CTRL, &req.position)
        .await?
        .res_result()?;

    Ok(Void)
}

#[derive(Deserialize)]
struct CargoCtrlReq {
    index: u8,
    position: i32,
}

async fn on_cargo_ctrl(req: Cbor<CargoCtrlReq>) -> Result<Void, AppErr> {
    let cp_cargo = match req.index {
        1 => address::CARGO1,
        2 => address::CARGO2,
        _ => return Err(AppErr::Static("invalid index")),
    };

    serialport::req(cp_cargo, cmd::cargo::CARGO_CTRL, &req.position)
        .await?
        .res_result()?;

    Ok(Void)
}

async fn on_cargo_scan(req: Cbor<u8>) -> Result<Cbor<String>, AppErr> {
    let cp_cargo = match req.0 {
        1 => address::CARGO1,
        2 => address::CARGO2,
        _ => return Err(AppErr::Static("invalid index")),
    };

    let frame = serialport::req(cp_cargo, cmd::cargo::SCAN, ())
        .await?;
    let barcode: &[u8] = frame.parse_res()?;
    let s = String::from_utf8(barcode.to_vec())?;
    cbor_ok(s)
}

#[derive(Deserialize)]
struct ScanLedCtrlReq {
    index: u8,
    ctrl: u8,
}

async fn on_scan_led_ctrl(req: Cbor<ScanLedCtrlReq>) -> Result<Void, AppErr> {
    let cp_cargo = match req.index {
        1 => address::CARGO1,
        2 => address::CARGO2,
        _ => return Err(AppErr::Static("invalid index")),
    };
    serialport::simple_req(cp_cargo, cmd::cargo::SCAN_LED_CTRL, req.ctrl)
        .await?
        .res_result()?;
    Ok(Void)
}

#[derive(Deserialize)]
struct RowColReq {
    index: u8,
    row: i32,
    col: i32,
}

impl BodyEncode for RowColReq {
    fn encode(self, buf: &mut bytes::BytesMut) {
        buf.put_i32(self.row);
        buf.put_i32(self.col);
    }
}

async fn on_row_col_ctrl(req: Cbor<RowColReq>) -> Result<Void, AppErr> {
    let cp_cargo = match req.index {
        1 => address::CARGO1,
        2 => address::CARGO2,
        _ => return Err(AppErr::Static("invalid index")),
    };
    serialport::req(cp_cargo, cmd::cargo::ROW_COL_CTRL, req.0)
        .await?
        .res_result()?;
    Ok(Void)
}

pub fn register() -> Router {
    Router::new()
        .route("/pick_ctrl", post(pick_ctrl))
        .route("/compr_ctrl", post(compr_ctrl))
        .route("/cargo_ctrl", post(on_cargo_ctrl))
        .route("/scan", post(on_cargo_scan))
        .route("/scan_led_ctrl", post(on_scan_led_ctrl))
        .route("/row_col_ctrl", post(on_row_col_ctrl))
}
